/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.swak.asm.compiler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.swak.utils.ClassHelper;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
 * CtClassBuilder is builder for CtClass
 * <p>
 * contains all the information, including:
 * <p>
 * class name, imported packages, super class name, implemented interfaces,
 * constructors, fields, methods.
 */
public class CtClassBuilder {

	private String className;

	private String superClassName = "java.lang.Object";

	private List<String> imports = new ArrayList<>();

	private Map<String, String> fullNames = new HashMap<>();

	private List<String> ifaces = new ArrayList<>();

	private List<String> constructors = new ArrayList<>();

	private List<String> fields = new ArrayList<>();

	private List<String> methods = new ArrayList<>();

	public String getClassName() {
		return className;
	}

	public void setClassName(String className) {
		this.className = className;
	}

	public String getSuperClassName() {
		return superClassName;
	}

	public void setSuperClassName(String superClassName) {
		this.superClassName = getQualifiedClassName(superClassName);
	}

	public List<String> getImports() {
		return imports;
	}

	public void addImports(String pkg) {
		int pi = pkg.lastIndexOf('.');
		if (pi > 0) {
			String pkgName = pkg.substring(0, pi);
			this.imports.add(pkgName);
			if (!pkg.endsWith(".*")) {
				fullNames.put(pkg.substring(pi + 1), pkg);
			}
		}
	}

	public List<String> getInterfaces() {
		return ifaces;
	}

	public void addInterface(String iface) {
		this.ifaces.add(getQualifiedClassName(iface));
	}

	public List<String> getConstructors() {
		return constructors;
	}

	public void addConstructor(String constructor) {
		this.constructors.add(constructor);
	}

	public List<String> getFields() {
		return fields;
	}

	public void addField(String field) {
		this.fields.add(field);
	}

	public List<String> getMethods() {
		return methods;
	}

	public void addMethod(String method) {
		this.methods.add(method);
	}

	/**
	 * get full qualified class name
	 * 
	 * @param className super class name, maybe qualified or not
	 */
	protected String getQualifiedClassName(String className) {
		if (className.contains(".")) {
			return className;
		}

		if (fullNames.containsKey(className)) {
			return fullNames.get(className);
		}

		return ClassHelper.forName(imports.toArray(new String[0]), className).getName();
	}

	/**
	 * build CtClass object
	 */
	public CtClass build(ClassLoader classLoader) throws NotFoundException, CannotCompileException {
		ClassPool pool = new ClassPool(true);
		pool.appendClassPath(new LoaderClassPath(classLoader));

		// create class
		CtClass ctClass = pool.makeClass(className, pool.get(superClassName));

		// add imported packages
		imports.stream().forEach(pool::importPackage);

		// add implemented interfaces
		for (String iface : ifaces) {
			ctClass.addInterface(pool.get(iface));
		}

		// add constructors
		for (String constructor : constructors) {
			ctClass.addConstructor(CtNewConstructor.make(constructor, ctClass));
		}

		// add fields
		for (String field : fields) {
			ctClass.addField(CtField.make(field, ctClass));
		}

		// add methods
		for (String method : methods) {
			ctClass.addMethod(CtNewMethod.make(method, ctClass));
		}

		return ctClass;
	}
}
